﻿using Silk.NET.Input;
using Silk.NET.OpenGL;
using System.Drawing;
using StbImageSharp;
using Silk.NET.Core.Native;

namespace Test
{

	internal class Learn_02_Texture : LearnClass
	{
		private static GL _gl;

		private static uint _vao;
        private static uint _vbotest;

        private static uint _vbo, _ebo;

        private static uint _shaderProgram;
        private static uint _texture, _texture2;

        string LearnClass.Name => "02: Texture";

		void LearnClass.OnKeyDown(IKeyboard keyboard, Key key, int keyCode)
		{
		}

		void LearnClass.OnLoad()
		{
			if (Program._Window == null)
			{
				throw new Exception("Need call after _Window init");
			}

			_gl = Program._Window.CreateOpenGL();
			_gl.ClearColor(Color.CornflowerBlue);

            // 绑定VAO
			_vao = _gl.GenVertexArray();
			_gl.BindVertexArray(_vao);
            float[] verticestest =
{
            //      aPosition       | aTexCoords
                 0.5f,  0.5f, 0.0f,  1.0f, 1.0f,
            };

            float[] vertices =
            {
            //      aPosition       | aTexCoords
                 0.5f,  0.5f, 0.0f,  1.0f, 1.0f,
                 0.5f, -0.5f, 0.0f,  1.0f, 0.0f,
                -0.5f, -0.5f, 0.0f,  0.0f, 0.0f,
                -0.5f,  0.5f, 0.0f,  0.0f, 1.0f
            };
            uint[] indices =
			{
				0u, 1u, 3u,
				1u, 2u, 3u
			};

            // 绑定EBO

            _ebo = _gl.GenBuffer();
            _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, _ebo);

            // 这些都是在VAO中进行的操作
            // 没错，EBO是和VAO强关联的，可以理解为VAO中存储了一个指针，指向绑定的EBO
            unsafe
            {
                fixed (uint* buf = indices)
                    _gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint)(indices.Length * sizeof(uint)), buf, BufferUsageARB.StaticDraw);

                // 启用顶点属性
                _gl.EnableVertexAttribArray(0); 
                _gl.EnableVertexAttribArray(1);
            }

            _vbo = _gl.GenBuffer(); // 新VBO
            _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);
            unsafe
            {
                fixed (float* buf = vertices)
                    _gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(vertices.Length * sizeof(float)), buf, BufferUsageARB.StaticDraw);

                // 设置顶点属性指针 可以理解为这是在VAO上进行的操作,把对VBO中数据的描述存到了一个VAO中
                _gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)0);
                //_gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)(3 * sizeof(float)));
            }

            _vbotest = _gl.GenBuffer();
            _gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbotest);

            unsafe
            {
                fixed (float* buf = vertices)
                    _gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(vertices.Length * sizeof(float)), buf, BufferUsageARB.StaticDraw);

                _gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), (void*)(3 * sizeof(float)));
            }

            _gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0); // 在VAO中绑定并解释过的VBO可以解绑，因为其信息已经被保存在了VertexAttribPointer中
            // 所以 VAO到底是什么？
            // 它其实就是VertexAttribPointer的数组 + 指向一个EBO的指针的那么个结构体
            // struct VAO { VertexAttribPointer[] ptrs; EBO* ptrEbo; } 类似这么个东西
            // VertexAttribPointer() 首先所做的就是对着当前绑定的VBO的地址设定为对应指针的所指向的地址 ，
            // 比如 VertexAttribPointer(0, ......) 就是将0号指针的目标指向当前绑定的VBO
            // VertexAttribPointer()的后续参数则是对这个指针的属性进行一个描述
            // 所以VAO不是单纯的顶点信息描述，而是指向特定VBO的顶点信息描述，它如果没有VBO，那就失去了意义
            // 不过你可以在一个VAO中绑定多个vbo来分别存储不同的信息

            // 解绑buffer，可以避免误操作错误的buffer导致的bug
            // 先解绑VAO，再解绑buffer，否则将会是把buffer从VAO解绑
            _gl.BindVertexArray(0);
            _gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
            _gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, 0);

            LoadTexture(Path.Combine(Def.TexturePath, "container.png"), ref _texture);
            LoadTexture(Path.Combine(Def.TexturePath, "awesomeface.png"), ref _texture2);

            var vert = "";
            var frag = "";
            ShaderUtil.ReadShader(Path.Combine(Def.ShaderPath, "02_Vert.glsl"), ref vert);
            ShaderUtil.ReadShader(Path.Combine(Def.ShaderPath, "02_Frag.glsl"), ref frag);

            // 创建shader
            if (!CreateShaderProgram(vert, frag, out _shaderProgram))
			{
                throw new Exception("Create Shader Error");
			}


        }

        unsafe void LoadTexture(string path, ref uint textureId, bool flip = true)
        {
            if (!Path.Exists(path))
                throw new Exception($"LoadTexture : {path} does not exist");
            StbImage.stbi_set_flip_vertically_on_load(flip? 1 : 0);
            ImageResult result = ImageResult.FromMemory(File.ReadAllBytes(path), ColorComponents.RedGreenBlueAlpha);
            textureId = _gl.GenTexture();

            _gl.ActiveTexture(TextureUnit.Texture0); // 绑定纹理前应该先Active一个纹理单元
            _gl.BindTexture(TextureTarget.Texture2D, textureId); // 将纹理绑定到当前Active的纹理单元

            _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureWrapS, (int)TextureWrapMode.Repeat);
            _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureWrapT, (int)TextureWrapMode.Repeat);
            _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMinFilter, (int)TextureMinFilter.Nearest);
            _gl.TexParameterI(GLEnum.Texture2D, GLEnum.TextureMagFilter, (int)TextureMagFilter.Nearest);

            fixed (byte* ptr = result.Data)
                // 这个操作会生成与当前绑定的纹理对象在同一个目标上的纹理，此处是TextureTarget.Texture2D
                _gl.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, (uint)result.Width,
                    (uint)result.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, ptr);

        }

        void LearnClass.OnRender(double deltaTime)
		{
			_gl.Clear(ClearBufferMask.ColorBufferBit);

            _gl.BindVertexArray(_vao);
            //_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _vbo);


            _gl.UseProgram(_shaderProgram);

            _gl.ActiveTexture(TextureUnit.Texture0);
            _gl.BindTexture(TextureTarget.Texture2D, _texture);
            _gl.ActiveTexture(TextureUnit.Texture1);
            _gl.BindTexture(TextureTarget.Texture2D, _texture2);

            _gl.Uniform1(_gl.GetUniformLocation(_shaderProgram, "texture1"), 0);
            _gl.Uniform1(_gl.GetUniformLocation(_shaderProgram, "texture2"), 1);

            unsafe
            {
                _gl.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, (void*)0);
            }
        }

        void LearnClass.OnUnload()
		{
		}

		void LearnClass.OnUpdate(double deltaTime)
		{
		}


		static public bool CreateShaderProgram(string vertCode, string fragCode, out uint program)
		{
			program = 0;
            var error = "";

            uint vertexShader = _gl.CreateShader(ShaderType.VertexShader);
            _gl.ShaderSource(vertexShader, vertCode);
            uint fragmentShader = _gl.CreateShader(ShaderType.FragmentShader);
            _gl.ShaderSource(fragmentShader, fragCode);

            _gl.CompileShader(vertexShader);
            _gl.GetShader(vertexShader, ShaderParameterName.CompileStatus, out int vStatus);
            if (vStatus != (int)GLEnum.True)
                error += "Vertex shader failed to compile: " + _gl.GetShaderInfoLog(vertexShader) + "\n";

            _gl.CompileShader(fragmentShader);
            _gl.GetShader(fragmentShader, ShaderParameterName.CompileStatus, out int fStatus);
            if (fStatus != (int)GLEnum.True)
                error += "Fragment shader failed to compile: " + _gl.GetShaderInfoLog(fragmentShader) + "\n";

            if (error != "")
            {
                Console.WriteLine(error, ConsoleColor.Red);
                _gl.DeleteShader(vertexShader);
                _gl.DeleteShader(fragmentShader);
                return false;
            }

            program = _gl.CreateProgram();
            _gl.AttachShader(program, vertexShader);
            _gl.AttachShader(program, fragmentShader);

            _gl.LinkProgram(program);

            _gl.GetProgram(program, ProgramPropertyARB.LinkStatus, out int lStatus);
            if (lStatus != (int)GLEnum.True)
                error += "Program failed to link: " + _gl.GetProgramInfoLog(program) + "\n";

            if (error != "")
            {
                Console.WriteLine(error, ConsoleColor.Red);
                _gl.DeleteShader(vertexShader);
                _gl.DeleteShader(fragmentShader);
                return false;
            }

            _gl.DetachShader(program, vertexShader);
            _gl.DetachShader(program, fragmentShader);
            _gl.DeleteShader(vertexShader);
            _gl.DeleteShader(fragmentShader);


            return true;
        }
	}
}
